
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	DocView.m
 *
 *	This class handles the scaling of the drawing view. It repositions
 *	The drawing view within itself if the drawing view is smaller than the
 *	size of the clip view. The size of the doc view is:
 *	MAX(clip view frame, drawing view frame).
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
 */

#import "DocView.h"

#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/Matrix.h>
#import <appkit/ScrollView.h>
#import <dpsclient/wraps.h>
#import <appkit/nextstd.h>

@implementation DocView

/*
*	Since there is only one subview its easier to keep track of it as an
*	instance variable rather than in the subview list.  Returns any the
*	previous drawing view.
*/
- setDrawView:aView
{
	id		oldView;

	oldView = drawviewId;
	[oldView  removeFromSuperview];

	[self  addSubview:aView];
	drawviewId = aView;
	
	return oldView;
}

- drawView
{
	return drawviewId;
}

- setZoomControl:anObject
{
	zoomControl = anObject;
	
	return self;
}

- zoomControl
{
	return zoomControl;
}

- setHitSetting:(float) value
{	
	hitsetting = value;

	return self;	
}

/*
*	Scale the hit setting. Using an unscaled hit setting would be like
*	using a boxing glove on a 400% scale.
*/
- (float) hitSetting
{	
	return hitsetting * (1.0/scale);
}

- setScale:(float)value
{
	scale = value;
	
	return self;
}

- (float) scale
{
	return  scale;
}

- setZooming:(BOOL)flag
{
	zooming = flag;
	
	return self;
}

- (BOOL) isZooming
{
	return zooming;
}

/*
*	This method is overridden from the View class. When the
*	user is zooming the cursor rect for the zoom cursor is set
*	the the frame of the drawing view.
*/
- resetCursorRects
{
	NXPoint		hotspot;

	NXRect		cursorRect, visRect;

	if (zooming)
	{
		if (!zoomCursor)
		{
			hotspot.x = hotspot.y = 8.0;
			zoomCursor = [NXCursor  newFromImage:[NXImage  newFromSection:"zoom.tiff"]];
			[zoomCursor setHotSpot:&hotspot];
		}

		[self  getVisibleRect:&visRect];
		[drawviewId getFrame:&cursorRect];
		NXIntersectionRect(&visRect, &cursorRect);
		[self addCursorRect:&cursorRect  cursor:zoomCursor];
	}

	return self;
}

/*
*	Messaged from the zoom matrix. Starts the zoom loop. At the next
*	mouse down in the drawing view, the scaleDrawView:toPoint: method
*	will be called.
*/
- zoom:sender
{
	[self  setZooming:YES];
	[self  setZoomControl:sender];
	[self  resetCursorRects];

	[window makeKeyAndOrderFront:self];

	return self;
}

/*
*	Sizes the drawing view from the old scale to the newscale.
*	The frame of the view is used instead of the bounds because
*	the bound will always be the same whereas the frame will
*	provide the size in default coordinates.
*/
- sizeDrawViewWithScale:(float) newscale
{
	float			scalefactor, sizewidth, sizeheight;

	NXRect		viewFrame;
	
	[drawviewId  getFrame:&viewFrame];
	scalefactor = newscale/scale;
	sizewidth = viewFrame.size.width*scalefactor;
	sizeheight = viewFrame.size.height*scalefactor;
	[drawviewId  sizeTo:rint(sizewidth) :rint(sizeheight)];

	return self;
}

/* 
*	Scales the drawing view from the old scale to the new scale.
*	The scale method is relative so we use the ratio
*	of the old scale to the new scale to obtain the scaling factor.
*/
- scaleDrawViewWithScale:(float) newscale
{
	float 	scalefactor;

	scalefactor = newscale/scale;
	[drawviewId  scale:scalefactor  :scalefactor];

	return self;
}

/*
*	Place the view passed in in the center of the doc view
*	if it is smaller than the size of the ClipView. Two passes are made
*	through the loop because adding or removing a horizontal or
*	vertical scrollbar will affect the size of the remaining dimension .
*/
- placeDrawView
{
	float			margin;
	
	NXSize		contSize, newSize, insetSize, delta;

	NXRect 		viewFrame;

	margin = 4 * OFFSET * scale;
	[drawviewId  getFrame:&viewFrame];
	[[superview  superview]  getContentSize:&contSize];

	newSize.width = MAX(viewFrame.size.width, contSize.width);
	newSize.height = MAX(viewFrame.size.height, contSize.height);

	delta.width = newSize.width - viewFrame.size.width;
	delta.height = newSize.height - viewFrame.size.height;
	if (delta.width != 0.0 || delta.height != 0.0)
	{
		if (delta.width < margin)
			newSize.width += margin - delta.width;
		if (delta.height < margin)
			newSize.height += margin - delta.height;
	}
	
	[self  sizeTo:newSize.width  :newSize.height];

	insetSize.width = floor((newSize.width - viewFrame.size.width)/2);
	insetSize.height =  floor((newSize.height - viewFrame.size.height)/2);
	[drawviewId  moveTo:insetSize.width  :insetSize.height];
	if (insetSize.width != 0 || insetSize.height != 0)
		[drawviewId  setClipping:YES];
	else
		[drawviewId setClipping:NO];

	return self;
}

/*
*	Scales the view to the tag of the selected matrix cell and then
*	scrolls to the point. The point is in window coordinates so
*	it must be converted to the drawing view  coordinates in order
*	to position it in the same relative place in the view.
*/
- scaleDrawViewToPoint:(NXPoint *) p;
{
	float			newscale;

	NXPoint		viewPt;

	[window invalidateCursorRectsForView:self];
	newscale = [zoomControl  selectedTag] * 0.01;
	if (drawviewId && newscale != scale)
	{
		viewPt = *p;
		[drawviewId  convertPoint:&viewPt  fromView:nil];
		[window disableDisplay];
			/* Size and then scale the drawing view. The frame of the */
			/* drawing view must reflect the changed size. Scaling */
			/* alone will not change the frame. */
			[self  sizeDrawViewWithScale:newscale];
			[self  scaleDrawViewWithScale:newscale];
			[self  setScale:newscale];
	
			[self  placeDrawView];
			[self  scrollDrawPoint:&viewPt  toWindowPoint:p];
		[window reenableDisplay];
		[[window contentView]  display];
	}
	[self  setZooming:NO];

	return self;
}

/*
 *	Use the point location relative to the window versus the point relative
 *	to the drawing view (before the scale) to determine where to scroll
 *	the drawing view. We want the same location in the drawing view to
 *	appear where the mouse downed occurred.  The window coordinates
 *	are used and then adjusted by the position of the scrolling frame
 *	because the presence or absence of scrollbars throws the use
 *	of the doc views coordinates awry.
 */	
- scrollDrawPoint:(const NXPoint *) aPt  toWindowPoint:(const NXPoint *) windowPt
{
	NXPoint		viewPt, scrollPt;

	NXRect 		clipFrame, visRect;

	[superview  getFrame:&clipFrame];
	if (frame.size.width > clipFrame.size.width || frame.size.height > clipFrame.size.height)
	{
		viewPt = *aPt;
		[drawviewId  convertPointToSuperview:&viewPt];

		scrollPt = *windowPt;
		[self  convertPoint:&scrollPt  fromView:nil];
		[self  getVisibleRect:&visRect];
		viewPt.x  -= (scrollPt.x - visRect.origin.x);
		viewPt.y  -= (scrollPt.y - visRect.origin.y);
		[self  scrollPoint:&viewPt];		
	}

	return self;
}

/*
 * This drawSelf method draws a border around the drawing view as well as a
 * drop shadow.  This is only done if it will be visible however. We tell if it is visible
 * if the rectangle passed in is not contained entirely within drawingView frame.
*/
- drawSelf:(NXRect *)r :(int) count
{
	NXRect		drawRect, dropRect, bRect, xRect, yRect;

	/* Calculate the rectangle of the draw view plus its drop shadow. */
	[drawviewId  getFrame:&drawRect];
	dropRect = bRect = drawRect;
	NXOffsetRect(&bRect, -rint(LINEWIDTH/2 * scale), -rint(LINEWIDTH/2 * scale));
	NXOffsetRect(&dropRect, rint(OFFSET * scale), rint(-OFFSET * scale));
	NXUnionRect(&dropRect, &bRect);
	if (!NXContainsRect(&bRect, r))
	{
		bRect = xRect = yRect = dropRect;
		NXIntersectionRect(&drawRect, &bRect);
		NXDivideRect(&xRect, &dropRect, bRect.size.width, 0);
		NXDivideRect(&yRect, &dropRect, bRect.size.height, 3);

		PSgsave();
			PSsetgray(NX_BLACK);
			PSrectclip(r->origin.x, r->origin.y, r->size.width, r->size.height);
			PSrectfill(xRect.origin.x, xRect.origin.y,
				xRect.size.width, xRect.size.height);
			PSrectfill(yRect.origin.x, yRect.origin.y,
				yRect.size.width, yRect.size.height);
			PSsetlinewidth(LINEWIDTH * scale);
			PSrectstroke(drawRect.origin.x, drawRect.origin.y,
				drawRect.size.width, drawRect.size.height);
		PSgrestore();
	}

	return self;			
}

@end

